/***************************************************************************
 *   Copyleft 2008-2009 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./vEngine3D.h"
#include "./Math/vMath.h"
#include "./Output/vConsole.h"
#include <IL/il.h>
#include <IL/ilut.h>
#include <GL/gl.h>
#include "../Window/Win32/vWin32MainWindow.h"
#include "../Macros/vLogMacros.h"

namespace Vendetta {
	vEngine3D* vSingleton<vEngine3D>::mpSingleton = vNull;

	vEngine3D::vEngine3D() {
		mpMainWindow = vNull;
		mpResourceManager = vNull;
		mpApi = vNull;
		mpInputSystem = vNull;
		mpSceneManager = vNull;
		mShallLeave = false;
		mpSingleton = this;
		mpBuffer = vNull;
	}

	vEngine3D::~vEngine3D() {	
	}

	vEngine3D::vEngine3D(const vEngine3D& engine) {
		//TODO error later
	}

	vEngine3D& vEngine3D::operator=(const vEngine3D& engine) {
		//TODO error later

		return *this;
	}

	/**************************************************************
	* Other helpful functions                                     *
	***************************************************************/
	vVoid vEngine3D::Init(vDeviceInfo* devInfoPtr) {
		mpMainWindow = new vWin32MainWindow(vString("Vendetta OpenGL"), devInfoPtr);
		mpApi = new vWin32GlApi;
		if (!mpApi->init()) {
			throw vFatalException("Can't initialize API due to some errors, check logs for more information.");
		}
		mpInputSystem = new vInputSystem;
		vConsole::Instance().SetSize(mpMainWindow->Width(), mpMainWindow->Height());
		vConsole::Instance().AddLine(true, "FPS: %f.0", 60.0f);
		
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

		GLfloat ambiref[] = {0.3f, 0.3f, 0.3f, 1.0f};
		GLfloat diffref[] = {0.6f, 0.6f, 0.6f, 1.0f};
		GLfloat specref[] = {0.0f, 0.7f, 0.0f, 1.0f};
		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambiref);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffref);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specref);
		glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 12);
		glEnable(GL_COLOR_MATERIAL);
		glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_NORMALIZE);

		ilInit();
		vResourcesLoader loader("res/resources.vrs");
		mpResourceManager = new vResourcesManager();
		mpResourceManager->LoadResources(loader);

		mpSceneManager = new vSceneManager;
		mpSceneManager->CreateCamera("FreeLook");

		vTexture2D* tex = mpResourceManager->Texture2D("CellText2D");
		if (tex) {
			LOGS_INFO("HERE");
			mTextureID = tex->ID();
		}

		vReal fLength = 10.0f;
		vArray<vVector3D> coords;
		vArray<vVector2D> texCoords;
		vArray<vColor> colors;
		vArray<vVector3D> normals;

		colors<<vColor::Red<<vColor::Green<<vColor::Blue<<vColor::White;

		normals<<vVector3D(0, 0, 1)<<vVector3D(0, 0, 1)<<vVector3D(0, 0, 1)<<vVector3D(0, 0, 1);

		texCoords<<vVector2D(1.0f, 1.0f);
		texCoords<<vVector2D(0.0f, 0.0f);
		texCoords<<vVector2D(0.0f, 1.0f);
		texCoords<<vVector2D(1.0f, 0.0f);

		coords<<vVector3D(-1.0f*fLength, 1.0f*fLength, -1.0f*fLength);
		coords<<vVector3D(1.0f*fLength, 1.0f*fLength, -1.0f*fLength);
		coords<<vVector3D(1.0f*fLength, -1.0f*fLength, -1.0f*fLength);
		coords<<vVector3D(-1.0f*fLength, -1.0f*fLength, -1.0f*fLength);
		
		mpBuffer = mpApi->mpVertexBufferManager->CreateVertexBuffer(coords, texCoords, normals, colors,
			vVertexBuffer::VVBF_XYZ | vVertexBuffer::VVBF_UV | vVertexBuffer::VVBF_NXNYNZ | vVertexBuffer::VVBF_RGBA,
			vVertexBuffer::VVBPT_QUADS, vVertexBufferManager::VVBT_HARDWARE, vVertexBuffer::VVBU_STATIC);
	}

	vVoid vEngine3D::Update() {
		vFrameListener::vFrameEvent fevent;
		fevent.TimeSinceLastFrame = mpApi->mpTimer->FrameTime();
		mpMainWindow->Update();
		mpApi->Update();

		vConsole::Instance().EditLine(0, "FPS: %.0f", mpApi->mpTimer->FPS());
		mpSceneManager->Update(fevent);

		for (vUInt ui = 0; ui < mFrameListeners.Size(); ui++) {
			mFrameListeners[ui]->Update(fevent);
		}
	}

	vVoid vEngine3D::Run() {
		while(!mShallLeave) {
			Update();
			Render();
		}
	}

	vVoid vEngine3D::Quit() {
		mShallLeave = true;
	}

	vVoid vEngine3D::Kill() {
		mFrameListeners.Clear();
		vDelete(mpSceneManager);
		vDelete(mpResourceManager);
		if (mpApi) {
			mpApi->kill();
		}
		vDelete(mpApi);
		vDelete(mpInputSystem);
		vDelete(mpMainWindow);
	}

	vVoid vEngine3D::Render() {
		vFrameListener::vFrameEvent fevent;
		fevent.TimeSinceLastFrame = mpApi->mpTimer->FrameTime();
		mpApi->mpRenderer->BeginFrame();
		
		mpSceneManager->SetupCamera();
		setupLights();
		vConsole::Instance().Render();
		for (vUInt ui = 0; ui < mFrameListeners.Size(); ui++) {
			mFrameListeners[ui]->BeginFrame(fevent);
		}

		glPushMatrix();
		drawRoom();
		glPopMatrix();

		for (vUInt ui = 0; ui < mFrameListeners.Size(); ui++) {
			mFrameListeners[ui]->EndFrame(fevent);
		}
		mpApi->mpRenderer->EndFrame();
	}

	vVoid vEngine3D::AddFrameListener(vFrameListener* list) {
		if (list) {
			mFrameListeners.AddAsLast(list);
		}
	}

	vVoid vEngine3D::drawRoom() {
		vReal fLength = 10.0f;
		mpApi->mpState->SetCullingMode(vState::CW);//glFrontFace(GL_CW);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, mTextureID);
		
		if (mpBuffer) {
			mpBuffer->Use();
		}
		//glDisable(GL_TEXTURE_2D);
		mpApi->mpState->SetCullingMode(vState::CCW);//glFrontFace(GL_CCW);
	}

	vVoid vEngine3D::setupLights() {
		GLfloat ambientLight[] = {0.1f, 0.1f, 0.1f, 1.0f};
		GLfloat diffuseLight[] = {0.5f, 0.5f, 0.5f, 1.0f};
		GLfloat specularLight[] = {0.7f, 0.7f, 0.7f, 1.0f};
		//vVector3D pos = mCamera.Position();
		//GLfloat lightPos[] = {pos.X, pos.Y, pos.Z, 0};
		//glEnable(GL_LIGHTING);
		mpApi->mpState->SetLighting(vState::ON);
		glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
		glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
		//glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
		glEnable(GL_LIGHT0);
	}
}